package cn.bluedot.interceptor;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;

import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.Configuration;

import cn.bluedot.bean.PageBean;

@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class }) })
public class PagePlugin implements Interceptor {
    /**
     * 默认参数
     */
    // 默认页码
    private Integer defaultPage;
    // 默认每条页数
    private Integer defaultPageSize;
    // 默认是否启用插件
    private Boolean defaultUseFlag;
    // 默认是否检测页码参数
    private Boolean defaultCheckFlag;
    // 默认是否清除最后一个order by后的语句
    private Boolean defaultCleanOrderBy;
    /**
     * 声明对象
     */
    private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
    /**
     * 声明对象
     */
    private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        
        StatementHandler stmtHandler = (StatementHandler) getProxyObject(invocation.getTarget());

        MetaObject metaStatementHandler = MetaObject.forObject(stmtHandler, DEFAULT_OBJECT_FACTORY,
                DEFAULT_OBJECT_WRAPPER_FACTORY);

        String sql = (String) metaStatementHandler.getValue("delegate.boundSql.sql");

        MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");
        // 检查该sql语句是否是查询语句
        // 如果不是，则直接抛给下一层解决
        if (!checkSelect(sql)) {
            return invocation.proceed();
        }

        BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");

        Object parameterObject = boundSql.getParameterObject();

        PageBean pageBean = getPageBeanForParamObj(parameterObject);

        // 没有获取分页参数，则代表不进行分页
        if (pageBean == null) {
            return invocation.proceed();
        }

        // 获取配置中是否开启了分页功能
        Boolean useFlag = pageBean.getUseFlag() == null ? this.defaultUseFlag : pageBean.getUseFlag();

        if (!useFlag) {
            return invocation.proceed();
        }

        // 获取相关的配置信息
        Integer pageNum = pageBean.getPage() == null ? defaultPage : pageBean.getPage();
        Integer pageSize = pageBean.getPageSize() == null ? defaultPageSize : pageBean.getPageSize();
        Boolean checkFlag = pageBean.getCheckFlag() == null ? defaultCheckFlag : pageBean.getCheckFlag();
        Boolean cleanOrderBy = pageBean.getCleanOrderBy() == null ? defaultCleanOrderBy : pageBean.getCleanOrderBy();

        int total = getTotal(invocation, metaStatementHandler, boundSql, cleanOrderBy);
        // 填入参数总条数
        pageBean.setTotle(total);
        // 计算总页数
        int totalPage = total % pageSize == 0 ? total / pageSize : total / pageSize + 1;
        // 检查当前页码的有校性
        checkPage(checkFlag, pageNum, totalPage);
        pageBean.setTotalPage(totalPage);
        // 修改sql语句
        return preparedSQL(invocation, metaStatementHandler, boundSql, pageNum, pageSize);
    }

    private Object preparedSQL(Invocation invocation, MetaObject metaStatementHandler, BoundSql boundSql,
            Integer pageNum, Integer pageSize) throws Exception {
        
        String sql = boundSql.getSql();
        String newSql = "select * from (" + sql + ") $_paging_table limit ?, ?";

        metaStatementHandler.setValue("delegate.boundSql.sql", newSql);
        Object statementObj = invocation.proceed();

        this.preparePageDataParams((PreparedStatement) statementObj, pageNum, pageSize);

        return statementObj;
    }

    private void preparePageDataParams(PreparedStatement statementObj, Integer pageNum, Integer pageSize)
            throws SQLException {
        
        int index = statementObj.getParameterMetaData().getParameterCount();
        statementObj.setInt(index - 1, (pageNum - 1) * pageSize);
        statementObj.setInt(index, pageSize);
    }

    private void checkPage(Boolean checkFlag, Integer pageNum, int totalPage) throws Exception {
        
        if (checkFlag) {
            if (pageNum > totalPage) {
                throw new Exception("查询失败，页码大于总页数");
            }
        }
    }

    private int getTotal(Invocation invocation, MetaObject metaStatementHandler, BoundSql boundSql,
            Boolean cleanOrderBy) throws Throwable {
        
        MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");
        // 配置对象
        Configuration cfg = mappedStatement.getConfiguration();
        // 当前需要执行的sql
        String sql = (String) metaStatementHandler.getValue("delegate.boundSql.sql");
        // 去掉orderBy语句
        if (cleanOrderBy) {
            sql = this.cleanOrderByForSql(sql);
        }
        String countSql = "select count(*) as total from (" + sql + ") $_paging";
        // 获取拦截方法参数，更具插件签名。知道是Connection对象
        Connection connection = (Connection) invocation.getArgs()[0];
        PreparedStatement ps = null;
        int total = 0;

        try {
            ps = connection.prepareStatement(countSql);
            BoundSql countBoundSql = new BoundSql(cfg, countSql, boundSql.getParameterMappings(),
                    boundSql.getParameterObject());

            ParameterHandler handler = new DefaultParameterHandler(mappedStatement, boundSql.getParameterObject(),
                    countBoundSql);

            handler.setParameters(ps);

            ResultSet rs = ps.executeQuery();

            while (rs.next()) {
                total = rs.getInt("total");
            }
        } finally {
            if (ps != null) {
                ps.close();
            }
        }

        return total;
    }

    private String cleanOrderByForSql(String sql) {
        
        StringBuilder sb = new StringBuilder();
        String newSql = sql.toLowerCase();

        if (newSql.indexOf("order") == -1) {
            return sql;
        }

        int index = newSql.lastIndexOf("order");

        return sb.substring(0, index).toString();
    }

    private PageBean getPageBeanForParamObj(Object parameterObject) throws Exception {
        
        PageBean pageBean = null;

        if (parameterObject == null) {
            return null;
        }

        if (parameterObject instanceof Map) {
            Map<String, Object> paramMap = (Map<String, Object>) parameterObject;
            Set<Entry<String, Object>> entrySet = paramMap.entrySet();

            for (Entry<String, Object> entry : entrySet) {
                Object value = entry.getValue();
                if (value instanceof PageBean) {
                    return (PageBean) value;
                }
            }
        } else if (parameterObject instanceof PageBean) {
            return (PageBean) parameterObject;
        } else {
            Field[] fields = parameterObject.getClass().getDeclaredFields();

            for (Field field : fields) {
                if (field.getType() == PageBean.class) {
                    PropertyDescriptor pd = new PropertyDescriptor(field.getName(), parameterObject.getClass());
                    Method method = pd.getReadMethod();
                    return (PageBean) method.invoke(parameterObject);
                }
            }
        }

        return pageBean;
    }

    private boolean checkSelect(String sql) {
        
        String trimSql = sql.trim();
        int index = trimSql.toLowerCase().indexOf("select");
        return index == 0;
    }

    private Object getProxyObject(Object target) {
        
        MetaObject metaStatementHandler = MetaObject.forObject(target, DEFAULT_OBJECT_FACTORY,
                DEFAULT_OBJECT_WRAPPER_FACTORY);

        Object object = null;

        while (metaStatementHandler.hasGetter("h")) {
            object = metaStatementHandler.getValue("h");
            metaStatementHandler = MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY);
        }

        if (object == null) {
            return target;
        }

        return object;
    }

    @Override
    public Object plugin(Object target) {

        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {

        // 从配置中获取参数
        String strDefaultPage = properties.getProperty("default.page", "1");
        String strDefaultPageSize = properties.getProperty("default.pageSize", "50");
        String strDefaultUserFlag = properties.getProperty("default.useFlag", "false");
        String strDefaultCheckFlag = properties.getProperty("default.checkFlag", "false");
        String strDefaultCleanOrderBy = properties.getProperty("default.cleanOrderBy", "false");
        System.out.println(strDefaultPage + ">>>>>>>>>>>>>" + strDefaultPageSize);
        this.defaultCheckFlag = Boolean.parseBoolean(strDefaultCheckFlag);
        this.defaultCleanOrderBy = Boolean.parseBoolean(strDefaultCleanOrderBy);
        this.defaultPage = Integer.parseInt(strDefaultPage);
        this.defaultPageSize = Integer.parseInt(strDefaultPageSize);
        this.defaultUseFlag = Boolean.parseBoolean(strDefaultUserFlag);
    }

}
